﻿using System.Collections.Generic;
using Microsoft.Xna.Framework.Graphics;
using DrawableObjects.Actors;
using DrawableObjects;
using InterfaceComponents;
using Microsoft.Xna.Framework;
using LightEffects;
using GlobalGameClasses;
using SpecialEffects;
using DrawableObjects.Actors.Projectiles;

namespace WorldView 
{
    public class View 
    {
        /// <summary>
        /// This class draws everything our game needs.
        /// </summary>
        private GraphicsDevice graphicsDevice
        {
            get;
            set;
        }

        private GenericDrawableList drawableList 
        {
            get;
            set;
        }

        private Camera cam 
        {
            get;
            set;
        }

        private GlobalLight globalLight
        {
            get;
            set;
        }

        private List<InterfaceComponent> HUD 
        {
            get;
            set;
        }

        private Texture2D HUDBackground;

        private Effect shader_ShadowMap 
        {
            get;
            set;
        }

        private Effect shader_Shockwave
        {
            get;
            set;
        }

        private Effect shader_Actor 
        {
            get;
            set;
        }

        private SpriteBatch spriteBatch 
        {
            get;
            set;
        }

        private Terrain terrain 
        {
            get;
            set;
        }

        private Effect shader_terrain
        {
            get;
            set;
        }

        private Effect shader_Explosion
        {
            get;
            set;
        }

        private Effect shader_ParticleEffect
        {
            get;
            set;
        }

        private RenderTarget2D shadowMap;

        private RenderTarget2D backgroundScene;

        private void DrawObjects() 
        {
            List<Actor> actors = drawableList.getActors();
            shader_Actor.Parameters["globalAmbient"].SetValue(Color.White.ToVector3());
            shader_Actor.Parameters["Ke"].SetValue(0.2f);
            shader_Actor.Parameters["Ka"].SetValue(0.2f);
            shader_Actor.Parameters["Kd"].SetValue(1.5f);
            shader_Actor.Parameters["Ks"].SetValue(0.5f);
            shader_Actor.Parameters["specularPower"].SetValue(100);
            shader_Actor.Parameters["spotPower"].SetValue(20);
            foreach (Actor a in actors) 
            {              
                DrawActor(a);
            }
        }

        private void DrawActor(Actor actor)
        {
            shader_Actor.Parameters["Texture"].SetValue(actor.texture);
            shader_Actor.Parameters["World"].SetValue(actor.worldMatrix);
            shader_Actor.Parameters["WVP"].SetValue(actor.worldMatrix * cam.viewMatrix * cam.projectionMatrix);
            shader_Actor.Parameters["eyePosition"].SetValue(Vector3.Transform(cam.position, Matrix.Invert(actor.worldMatrix)));

            spriteBatch.GraphicsDevice.BlendState = BlendState.Opaque;
            spriteBatch.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            shader_Actor.CurrentTechnique.Passes["Ambient"].Apply();

            foreach (ModelMesh mesh in actor.model.Meshes)
            {
                foreach (ModelMeshPart meshPart in mesh.MeshParts)
                {
                    shader_Actor.Parameters["Texture"].SetValue(actor.texture);
                    meshPart.Effect = shader_Actor;
                }
                mesh.Draw();
            }

            spriteBatch.GraphicsDevice.BlendState = BlendState.Additive;
            
            //Global Light
            shader_Actor.Parameters["lightColor"].SetValue(globalLight.color);
            shader_Actor.Parameters["lightDirection"].SetValue(globalLight.direction);
            shader_Actor.CurrentTechnique.Passes["Directional"].Apply();
            foreach (ModelMesh mesh in actor.model.Meshes)
            {
                mesh.Draw();
            }

            //PointLight
            if(actor is Projectile) {
                Projectile p = (Projectile) actor;
                if (p.light != null)
                {
                    shader_Actor.Parameters["lightColor"].SetValue(p.light.color);
                    Vector3 pos = p.worldMatrix.Translation;
                    pos.Y += 5;
                    pos.Z += 5;
                    shader_Actor.Parameters["lightPosition"].SetValue(pos);
                    shader_Actor.CurrentTechnique.Passes["Point"].Apply();
                    foreach (ModelMesh mesh in actor.model.Meshes)
                    {
                        mesh.Draw();
                    }
                }
            }
        }

        private void DrawHUD() 
        {
            
            spriteBatch.Begin();
            spriteBatch.Draw(HUDBackground, new Rectangle(870, 0, 250, 630), Color.White);
            foreach (InterfaceComponent comp in HUD) 
            {
                comp.Draw(Color.LightGray);
            }
            spriteBatch.End();
        }

        public View(GenericDrawableList drawableList, Effect shader_Actor, Camera cam, List<InterfaceComponent> HUD, Effect shader_ShadowMap, SpriteBatch spriteBatch, Terrain terrain, Effect shader_terrain, GlobalLight globalLight, string HUDBackground, Effect shader_explosion, Effect shader_ParticleEffect, Effect shader_shockwave) 
        {
            this.drawableList = drawableList;
            this.shader_Actor = shader_Actor;
            this.shader_terrain = shader_terrain;
            this.cam = cam;
            this.HUD = HUD;
            this.shader_ShadowMap = shader_ShadowMap;
            this.spriteBatch = spriteBatch;
            this.terrain = terrain;
            this.globalLight = globalLight;
            this.shader_Explosion = shader_explosion;
            this.shader_ParticleEffect = shader_ParticleEffect;
            this.shader_Shockwave = shader_shockwave;
            if(!GlobalGameValues.Values.content.Textures.TryGetValue(HUDBackground, out this.HUDBackground))
            {
                throw new System.ArgumentException("Couldn't find HUDBackground-Texture");
            }
            graphicsDevice = GlobalGameValues.Values.graphicsDevice;
            shadowMap = new RenderTarget2D(spriteBatch.GraphicsDevice, 4096, 4096, false, SurfaceFormat.Single, DepthFormat.Depth24);
            PresentationParameters pp = graphicsDevice.PresentationParameters;
            backgroundScene = new RenderTarget2D(graphicsDevice, pp.BackBufferWidth, pp.BackBufferHeight, true, graphicsDevice.DisplayMode.Format, DepthFormat.Depth24);
                    
        }

        /// <summary>
        /// this method draws everything in the game
        /// </summary>
        public void Draw() 
        {
            cam.Update();
            DrawTerrain();
            DrawObjects();
            DrawSpecialEffects();
            DrawHUD();
            globalLight.Update();
        }


        

        private void DrawSpecialEffects()
        {
            List<SpecialEffect> specialEffects = drawableList.getSpecialEffects();
            if (specialEffects != null)
            {
                foreach (SpecialEffect specialEffect in specialEffects)
                {
                    if (specialEffect is BasicExplosion)
                    {
                        shader_Explosion.CurrentTechnique = shader_Explosion.Techniques["Explosion"];
                        shader_Explosion.Parameters["xWorld"].SetValue(Matrix.Identity);
                        shader_Explosion.Parameters["xView"].SetValue(cam.viewMatrix);
                        shader_Explosion.Parameters["xProjection"].SetValue(cam.projectionMatrix);
                        shader_Explosion.Parameters["xCamPos"].SetValue(cam.position1);
                        shader_Explosion.Parameters["xCamUp"].SetValue(Vector3.Up);
                        shader_Explosion.Parameters["xTime"].SetValue((float)GlobalGameValues.Values.gameTime.TotalGameTime.TotalMilliseconds);

                        specialEffect.Draw(shader_Explosion);
                    }

                    if (specialEffect is BasicFireAndSmoke)
                    {

                        shader_ParticleEffect.CurrentTechnique = shader_ParticleEffect.Techniques["Technique1"];
                        shader_ParticleEffect.Parameters["View"].SetValue(cam.viewMatrix);
                        shader_ParticleEffect.Parameters["Projection"].SetValue(cam.projectionMatrix);
                        shader_ParticleEffect.Parameters["CurrentTime"].SetValue((float)GlobalGameValues.Values.gameTime.TotalGameTime.TotalMilliseconds);
                        //the relational up vector of the camera, orthogonal vector to the plane of eyevector and side vector of the camera
                        //needed to face every particle to the camera's eye
                        shader_ParticleEffect.Parameters["Up"].SetValue(Vector3.Normalize(Vector3.Cross(cam.eyeVector, Vector3.Right)));
                        shader_ParticleEffect.Parameters["Side"].SetValue(Vector3.Right);

                        specialEffect.Draw(shader_ParticleEffect);
                    }
                }
            }
        }

        public void DrawTerrain()
        {
            //Checks whether the camera has passed one part of the terrain.
            if (cam.distanceToStart - terrain.terrainHeight_part / 20 > (terrain.progress + 1) * terrain.terrainHeight_part)
            {
                terrain.progress++;
                terrain.Update();
            }

            //sets up parameters to the shader
            shader_terrain.CurrentTechnique = shader_terrain.Techniques["TerrainTextures"];
            foreach (EffectPass pass in shader_terrain.CurrentTechnique.Passes)
            {
                pass.Apply();
            }

            shader_terrain.Parameters["xView"].SetValue(cam.viewMatrix);
            shader_terrain.Parameters["xProjection"].SetValue(cam.projectionMatrix);
            Vector3 lightDirection = globalLight.direction;
            lightDirection.Normalize();
            shader_terrain.Parameters["xLightDirection"].SetValue(lightDirection);
            shader_terrain.Parameters["xAmbientColor"].SetValue(new Vector4(0.05f, 0.05f, 0.05f, 0));
            globalLight.createShadowMap(spriteBatch.GraphicsDevice, drawableList.getActors().ToArray(), cam, shader_ShadowMap, shadowMap);
            shader_terrain.Parameters["xLightViewProj"].SetValue(globalLight.CreateLightViewProjectionMatrix(cam));
            shader_terrain.Parameters["xShadowMap"].SetValue(shadowMap);
            terrain.Draw(shader_terrain);
            //drawShadowMapToScreen(shadowMap);
        }

        private void drawShadowMapToScreen(RenderTarget2D ShadowMap)
        {
            spriteBatch.Begin(0, BlendState.Opaque, SamplerState.PointClamp, null, null);
            spriteBatch.Draw(ShadowMap, new Rectangle(0, 0, 128, 128), Color.White);
            spriteBatch.End();
        }
    }
}

